#include "vartab.hpp"
#include "error.hpp"
#include <stdio.h>
#include "parser.hpp"

// Arguments.

vector<Properties*> Arguments::_arglist;

void Arguments::reset() {
    _arglist.clear();
}

void Arguments::addarg(string name, Type* type) {
    _arglist.push_back(new Properties("", name, type));
}

vector<Properties*> Arguments::arglist() {
    return _arglist;
}

// Function.

Function::Function(string n, vector<Properties*> p, Type* r, string c) {
    _name = n;
    _parameters = vector<Properties*>(p);
    _returntype = r;
    _code = c;
    _returner = Scope::returner();
    _exitpoint = Labels::functionexit(Scope::level());
}

string Function::callcode(vector<Properties*> arguments) {
    string code;
    int i;
    int size = arguments.size();
    for (i = 0; i < size; i++) {
        code += _parameters[i]->value + " = " + arguments[size - i - 1]->value + ";\n";
    }
    code += _code + _exitpoint + ":\n";
    return code;
}

string Function::dec() {
    string s = _name + ":(";
    int i;
    for (i = _parameters.size(); i > 0; i--) {
        s += _parameters[i - 1]->type->name();
        if ((i - 1) != 0)
            s += ", ";
    }
    if (_returntype)
        s += ")->" + _returntype->name();
    else
        s += ")";
    return s;
}

bool Function::operator ==(Function& other) {
    if (_name != other._name)
        return false;
    if (_parameters.size() != other._parameters.size())
        return false;
    int i;
    for (i = 0; i < _parameters.size(); i++) {
        Type *t1 = _parameters[i]->type;
        Type *t2 = other._parameters[i]->type;
        if (!(*t1 = *t2) && !(*t2 = *t1))
            return false;
    }
    return true;
}

bool Function::operator ==(vector<Properties*> args) {
    if (_parameters.size() != args.size())
        return false;
    int i;
    for (i = 0; i < _parameters.size(); i++) {
        Properties *p1 = _parameters[i];
        Properties *p2 = args[i];
        if (!Operation::binop(*p1, *p2, OP_ASG))
            return false;
    }
    return true;
}

// Scope.

bool Scope::_local = false;

int Scope::_level = 0;

bool Scope::atlocal() {
    return _local;
}

int Scope::level() {
    return _level;
}

void Scope::enterlocal() {
    _local = true;
}

void Scope::changelocal() {
    _level++;
}

void Scope::leavelocal() {
    _local = false;
    _level++;
}

string Scope::preffix() {
    if (_local) {
        char preffix[16];
        sprintf(preffix, "local_%03d_", _level);
        return preffix;
    } else
        return "";
}

string Scope::returner() {
    char num[20];
    sprintf(num, "return_%03d", _level);
    return num;
}

// SymbolTable member functions.
set<Function*> SymbolTable::_functions;
map<string, Type*> SymbolTable::_symbols;
map<Type*, int> SymbolTable::_temps;
map<Type*, string> SymbolTable::_swapaux;

void SymbolTable::insvar(string name, Type* type) {
    name = Scope::preffix() + name;
    if (_symbols.find(name) != _symbols.end())
        Error::dupid(name, type);
    _symbols[name] = type;
}

void SymbolTable::insmap(string name, Type* type, string code) {
    Function* newmap = new Function(name, Arguments::arglist(), type, code);
    Arguments::reset();
    set<Function*>::iterator f;
    for(f = _functions.begin(); f != _functions.end(); f++)
        if(*newmap == **f)
            Error::dupmap(*(_functions.find(newmap)), newmap);
    _functions.insert(newmap);
    _symbols[Scope::returner()] = type;
    Scope::changelocal();
}

Properties* SymbolTable::checkvar(string name) {
    string localname = Scope::preffix() + name;
    if (_symbols.find(localname) != _symbols.end())
        return new Properties("", localname, _symbols[localname]);
    else if (_symbols.find(name) != _symbols.end())
        return new Properties("", name, _symbols[name]);
    else
        Error::decmiss(name);
    return NULL;
}

string SymbolTable::allcdecs() {
    map<string, Type*>::iterator i;
    string alldecs = "";
    for (i = _symbols.begin(); i != _symbols.end(); i++)
        alldecs += i->second->cdec(i->first) + ";\n";
    map<Type*, string>::iterator j;
    for (j = _swapaux.begin(); j != _swapaux.end(); j++) {
        alldecs += j->first->cdec(j->second) + ";\n";
    }
    return alldecs;
}

Properties* SymbolTable::invoke(string name) {
    Properties* result = new Properties();
    vector<Properties*> args = Arguments::arglist();
    Function* call = new Function(name, args, NULL, "");
    Function* right = NULL;
    set<Function*>::iterator i;
    for (i = _functions.begin(); right == NULL && i != _functions.end(); i++) {
        if (**i == args)
            right = *i;
    }
    if (right == NULL)
        Error::mapundef(call);
    result->type = right->_returntype;
    result->value = right->_returner;
    result->code = right->callcode(args);
    Arguments::reset();
    return result;
}

string SymbolTable::newtemp(Type* type) {
    char num[256];
    if (_temps.find(type) == _temps.end())
        _temps[type] = 0;
    sprintf(num, "temp%s_%03d", type->tempsuffix().c_str(), _temps[type]);
    _temps[type] += 1;
    string newvar(num);
    _symbols[newvar] = type;
    return newvar;
}

string SymbolTable::swapaux(Type* type) {
    if (_swapaux.find(type) == _swapaux.end())
        _swapaux[type] = "temp" + type->tempsuffix() + "_swapaux";
    return _swapaux[type];
}


// Operation member functions.

Type* Operation::binop(Properties& v1, Properties& v2, Operator op) {
    Type *t1 = v1.type, *t2 = v2.type;
    Type* result = NULL;
    switch (op) {
        case OP_SUM:
            result = (*t1 + *t2);
            break;
        case OP_SUB:
            result = (*t1 - *t2);
            break;
        case OP_MUL:
            result = (*t1 * *t2);
            break;
        case OP_DIV:
            result = (*t1 / *t2);
            break;
        case OP_MOD:
            result = (*t1 % *t2);
            break;
        case OP_ASG:
            if (t1->receives(t2))
                result = t1;
            else
                result = NULL;
            break;
        case OP_EQL:
            if (t1->equals(t2))
                result = Logic::type();
            else
                result = NULL;
            break;
        case OP_DIF:
            if(t1->equals(t2))
                result = Logic::type();
            else
                result = NULL;
            break;
        case OP_LES:
            result = (*t1 < *t2);
            break;
        case OP_GRT:
            result = (*t1 > *t2);
            break;
        case OP_LEQ:
            result = (*t1 <= *t2);
            break;
        case OP_GEQ:
            result = (*t1 >= *t2);
            break;
        case OP_AND:
            result = (*t1 && *t2);
            break;
        case OP_OR:
            result = (*t1 || *t2);
            break;
        default:
            Error::panic(__FILE__, __LINE__, "Operação binária não reconhecida");
    }
    if(result == NULL)
        Error::opundef(v1, v2, opstr(op));
    return result;
}

Type* Operation::unop(Properties& t1, Operator op) {
    switch (op) {
        case OP_NEG:
            return ! *t1.type;
        case OP_SUB:
            return - *t1.type;
        case OP_DRF:
            return * *t1.type;
        default:
            Error::panic(__FILE__, __LINE__, "Operação unária não reconhecida");
    }
    return NULL;
}

string Operation::opstr(Operator op) {
    switch (op) {
        case OP_SUM:
            return "+";
        case OP_SUB:
            return "-";
        case OP_MUL:
            return "*";
        case OP_DIV:
            return "/";
        case OP_MOD:
            return "%";
        case OP_ASG:
            return "=";
        case OP_EQL:
            return "==";
        case OP_DIF:
            return "!=";
        case OP_LES:
            return "<";
        case OP_GRT:
            return ">";
        case OP_LEQ:
            return "<=";
        case OP_GEQ:
            return ">=";
        case OP_AND:
            return "&&";
        case OP_OR:
            return "||";
        case OP_NEG:
            return "!";
        case OP_DRF:
            return "*";
        default:
            Error::panic(__FILE__, __LINE__, "Operador não reconhecido");
    }
    return NULL;
}